//
// "$Id: Fl_Color_Chooser.cxx 9673 2012-08-18 10:47:48Z AlbrechtS $"
//
// Color chooser for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2010 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     http://www.fltk.org/COPYING.php
//
// Please report all bugs and problems on the following page:
//
//     http://www.fltk.org/str.php
//

#include "Fl.H"
#include "Fl_Color_Chooser.H"
#include "fl_draw.H"
#include "fltkmath.h"
#include <stdio.h>

// Besides being a useful object on it's own, the Fl_Color_Chooser was
// an attempt to make a complex composite object that could be easily
// imbedded into a user interface.  If you wish to make complex objects
// of your own, be sure to read this code.

// The function fl_color_chooser() creates a window containing a color
// chooser and a few buttons and current-color indicators.  It is an
// easier interface for simple programs that just need a color.

// The "hue box" can be a circle or rectilinear.
// You get a circle by defining this:
#define CIRCLE 1
// And the "hue box" can auto-update when the value changes
// you get this by defining this:
#define UPDATE_HUE_BOX 1

/**
  This \e static method converts HSV colors to RGB colorspace.
  \param[in] H, S, V color components
  \param[out] R, G, B color components
 */
void Fl_Color_Chooser::hsv2rgb(
        double H, double S, double V, double& R, double& G, double& B)
{
	if (S < 5.0e-6) {
		R = G = B = V;
	} else {
		int i = (int)H;
		double f = H - (float)i;
		double p1 = V*(1.0-S);
		double p2 = V*(1.0-S*f);
		double p3 = V*(1.0-S*(1.0-f));
		switch (i) {
		case 0:
			R = V;
			G = p3;
			B = p1;
			break;
		case 1:
			R = p2;
			G = V;
			B = p1;
			break;
		case 2:
			R = p1;
			G = V;
			B = p3;
			break;
		case 3:
			R = p1;
			G = p2;
			B = V;
			break;
		case 4:
			R = p3;
			G = p1;
			B = V;
			break;
		case 5:
			R = V;
			G = p1;
			B = p2;
			break;
		}
	}
}

/**
  This \e static method converts RGB colors to HSV colorspace.
  \param[in] R, G, B color components
  \param[out] H, S, V color components
 */
void Fl_Color_Chooser::rgb2hsv(
        double R, double G, double B, double& H, double& S, double& V)
{
	double maxv = R > G ? R : G;
	if (B > maxv) maxv = B;
	V = maxv;
	if (maxv>0) {
		double minv = R < G ? R : G;
		if (B < minv) minv = B;
		S = 1.0 - double(minv)/maxv;
		if (maxv > minv) {
			if (maxv == R) {
				H = (G-B)/double(maxv-minv);
				if (H<0) H += 6.0;
			} else if (maxv == G) H = 2.0+(B-R)/double(maxv-minv);
			else H = 4.0+(R-G)/double(maxv-minv);
		}
	}
}

/** Fl_Color_Chooser modes */
enum {
	M_RGB,	/**< mode() of Fl_Color_Chooser showing RGB values */
	M_BYTE,	/**< mode() of Fl_Color_Chooser showing byte values */
	M_HEX,	/**< mode() of Fl_Color_Chooser showing hex values */
	M_HSV		/**< mode() of Fl_Color_Chooser showing HSV values */
};
static const Fl_Menu_Item mode_menu[] = {
	{"rgb"},
	{"byte"},
	{"hex"},
	{"hsv"},
	{0}
};

#ifndef FL_DOXYGEN
int Flcc_Value_Input::format(char* buf)
{
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();
	if (c->mode() == M_HEX) return sprintf(buf,"0x%02X", int(value()));
	else return Fl_Valuator::format(buf);
}
#endif // !FL_DOXYGEN

void Fl_Color_Chooser::set_valuators()
{
	switch (mode()) {
	case M_RGB:
		rvalue.range(0,1);
		rvalue.step(1,1000);
		rvalue.value(r_);
		gvalue.range(0,1);
		gvalue.step(1,1000);
		gvalue.value(g_);
		bvalue.range(0,1);
		bvalue.step(1,1000);
		bvalue.value(b_);
		break;
	case M_BYTE: /* FALLTHROUGH */
	case M_HEX:
		rvalue.range(0,255);
		rvalue.step(1);
		rvalue.value(int(255*r_+.5));
		gvalue.range(0,255);
		gvalue.step(1);
		gvalue.value(int(255*g_+.5));
		bvalue.range(0,255);
		bvalue.step(1);
		bvalue.value(int(255*b_+.5));
		break;
	case M_HSV:
		rvalue.range(0,6);
		rvalue.step(1,1000);
		rvalue.value(hue_);
		gvalue.range(0,1);
		gvalue.step(1,1000);
		gvalue.value(saturation_);
		bvalue.range(0,1);
		bvalue.step(1,1000);
		bvalue.value(value_);
		break;
	}
}

/**
  Sets the current rgb color values.
  Does not do the callback. Does not clamp (but out of range values will
  produce psychedelic effects in the hue selector).
  \param[in] R, G, B color components.
  \return 1 if a new rgb value was set, 0 if the rgb value was the previous one.
 */
int Fl_Color_Chooser::rgb(double R, double G, double B)
{
	if (R == r_ && G == g_ && B == b_) return 0;
	r_ = R;
	g_ = G;
	b_ = B;
	double ph = hue_;
	double ps = saturation_;
	double pv = value_;
	rgb2hsv(R,G,B,hue_,saturation_,value_);
	set_valuators();
	set_changed();
	if (value_ != pv) {
#ifdef UPDATE_HUE_BOX
		huebox.damage(FL_DAMAGE_SCROLL);
#endif
		valuebox.damage(FL_DAMAGE_EXPOSE);
	}
	if (hue_ != ph || saturation_ != ps) {
		huebox.damage(FL_DAMAGE_EXPOSE);
		valuebox.damage(FL_DAMAGE_SCROLL);
	}
	return 1;
}

/**
  Set the hsv values.
  The passed values are clamped (or for hue, modulus 6 is used) to get
  legal values. Does not do the callback.
  \param[in] H, S, V color components.
  \return 1 if a new hsv value was set, 0 if the hsv value was the previous one.
*/
int Fl_Color_Chooser::hsv(double H, double S, double V)
{
	H = fmod(H,6.0);
	if (H < 0.0) H += 6.0;
	if (S < 0.0) S = 0.0;
	else if (S > 1.0) S = 1.0;
	if (V < 0.0) V = 0.0;
	else if (V > 1.0) V = 1.0;
	if (H == hue_ && S == saturation_ && V == value_) return 0;
	double ph = hue_;
	double ps = saturation_;
	double pv = value_;
	hue_ = H;
	saturation_ = S;
	value_ = V;
	if (value_ != pv) {
#ifdef UPDATE_HUE_BOX
		huebox.damage(FL_DAMAGE_SCROLL);
#endif
		valuebox.damage(FL_DAMAGE_EXPOSE);
	}
	if (hue_ != ph || saturation_ != ps) {
		huebox.damage(FL_DAMAGE_EXPOSE);
		valuebox.damage(FL_DAMAGE_SCROLL);
	}
	hsv2rgb(H,S,V,r_,g_,b_);
	set_valuators();
	set_changed();
	return 1;
}

////////////////////////////////////////////////////////////////

static void tohs(double x, double y, double& h, double& s)
{
#ifdef CIRCLE
	x = 2*x-1;
	y = 1-2*y;
	s = sqrt(x*x+y*y);
	if (s > 1.0) s = 1.0;
	h = (3.0/M_PI)*atan2(y,x);
	if (h<0) h += 6.0;
#else
	h = fmod(6.0*x,6.0);
	if (h < 0.0) h += 6.0;
	s = 1.0-y;
	if (s < 0.0) s = 0.0;
	else if (s > 1.0) s = 1.0;
#endif
}

#ifndef FL_DOXYGEN
int Flcc_HueBox::handle(int e)
{
	static double ih, is;
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();
	switch (e) {
	case FL_PUSH:
		if (Fl::visible_focus()) {
			Fl::focus(this);
			redraw();
		}
		ih = c->hue();
		is = c->saturation();
	case FL_DRAG: {
		double Xf, Yf, H, S;
		Xf = (Fl::event_x()-x()-Fl::box_dx(box()))/double(w()-Fl::box_dw(box()));
		Yf = (Fl::event_y()-y()-Fl::box_dy(box()))/double(h()-Fl::box_dh(box()));
		tohs(Xf, Yf, H, S);
		if (fabs(H-ih) < 3*6.0/w()) H = ih;
		if (fabs(S-is) < 3*1.0/h()) S = is;
		if (Fl::event_state(FL_CTRL)) H = ih;
		if (c->hsv(H, S, c->value())) c->do_callback();
	}
	return 1;
	case FL_FOCUS : /* FALLTHROUGH */
	case FL_UNFOCUS :
		if (Fl::visible_focus()) {
			redraw();
			return 1;
		} else return 1;
	case FL_KEYBOARD :
		return handle_key(Fl::event_key());
	default:
		return 0;
	}
}
#endif // !FL_DOXYGEN

static void generate_image(void* vv, int X, int Y, int W, uchar* buf)
{
	Flcc_HueBox* v = (Flcc_HueBox*)vv;
	int iw = v->w()-Fl::box_dw(v->box());
	double Yf = double(Y)/(v->h()-Fl::box_dh(v->box()));
#ifdef UPDATE_HUE_BOX
	const double V = ((Fl_Color_Chooser*)(v->parent()))->value();
#else
	const double V = 1.0;
#endif
	for (int x = X; x < X+W; x++) {
		double Xf = double(x)/iw;
		double H,S;
		tohs(Xf,Yf,H,S);
		double r=0, g=0, b=0;
		Fl_Color_Chooser::hsv2rgb(H,S,V,r,g,b);
		*buf++ = uchar(255*r+.5);
		*buf++ = uchar(255*g+.5);
		*buf++ = uchar(255*b+.5);
	}
}

#ifndef FL_DOXYGEN
int Flcc_HueBox::handle_key(int key)
{
	int w1 = w()-Fl::box_dw(box())-6;
	int h1 = h()-Fl::box_dh(box())-6;
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();

#ifdef CIRCLE
	int X = int(.5*(cos(c->hue()*(M_PI/3.0))*c->saturation()+1) * w1);
	int Y = int(.5*(1-sin(c->hue()*(M_PI/3.0))*c->saturation()) * h1);
#else
	int X = int(c->hue()/6.0*w1);
	int Y = int((1-c->saturation())*h1);
#endif

	switch (key) {
	case FL_Up :
		Y -= 3;
		break;
	case FL_Down :
		Y += 3;
		break;
	case FL_Left :
		X -= 3;
		break;
	case FL_Right :
		X += 3;
		break;
	default :
		return 0;
	}

	double Xf, Yf, H, S;
	Xf = (double)X/(double)w1;
	Yf = (double)Y/(double)h1;
	tohs(Xf, Yf, H, S);
	if (c->hsv(H, S, c->value())) c->do_callback();

	return 1;
}
#endif // !FL_DOXYGEN

#ifndef FL_DOXYGEN
void Flcc_HueBox::draw()
{
	if (damage()&FL_DAMAGE_ALL) draw_box();
	int x1 = x()+Fl::box_dx(box());
	int yy1 = y()+Fl::box_dy(box());
	int w1 = w()-Fl::box_dw(box());
	int h1 = h()-Fl::box_dh(box());
	if (damage() == FL_DAMAGE_EXPOSE) fl_push_clip(x1+px,yy1+py,6,6);
	fl_draw_image(generate_image, this, x1, yy1, w1, h1);
	if (damage() == FL_DAMAGE_EXPOSE) fl_pop_clip();
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();
#ifdef CIRCLE
	int X = int(.5*(cos(c->hue()*(M_PI/3.0))*c->saturation()+1) * (w1-6));
	int Y = int(.5*(1-sin(c->hue()*(M_PI/3.0))*c->saturation()) * (h1-6));
#else
	int X = int(c->hue()/6.0*(w1-6));
	int Y = int((1-c->saturation())*(h1-6));
#endif
	if (X < 0) X = 0;
	else if (X > w1-6) X = w1-6;
	if (Y < 0) Y = 0;
	else if (Y > h1-6) Y = h1-6;
	//  fl_color(c->value()>.75 ? FL_BLACK : FL_WHITE);
	draw_box(FL_UP_BOX,x1+X,yy1+Y,6,6,Fl::focus() == this ? FL_FOREGROUND_COLOR : FL_GRAY);
	px = X;
	py = Y;
}
#endif // !FL_DOXYGEN

////////////////////////////////////////////////////////////////

#ifndef FL_DOXYGEN
int Flcc_ValueBox::handle(int e)
{
	static double iv;
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();
	switch (e) {
	case FL_PUSH:
		if (Fl::visible_focus()) {
			Fl::focus(this);
			redraw();
		}
		iv = c->value();
	case FL_DRAG: {
		double Yf;
		Yf = 1-(Fl::event_y()-y()-Fl::box_dy(box()))/double(h()-Fl::box_dh(box()));
		if (fabs(Yf-iv)<(3*1.0/h())) Yf = iv;
		if (c->hsv(c->hue(),c->saturation(),Yf)) c->do_callback();
	}
	return 1;
	case FL_FOCUS : /* FALLTHROUGH */
	case FL_UNFOCUS :
		if (Fl::visible_focus()) {
			redraw();
			return 1;
		} else return 1;
	case FL_KEYBOARD :
		return handle_key(Fl::event_key());
	default:
		return 0;
	}
}
#endif // !FL_DOXYGEN

static double tr, tg, tb;
static void generate_vimage(void* vv, int X, int Y, int W, uchar* buf)
{
	Flcc_ValueBox* v = (Flcc_ValueBox*)vv;
	double Yf = 255*(1.0-double(Y)/(v->h()-Fl::box_dh(v->box())));
	uchar r = uchar(tr*Yf+.5);
	uchar g = uchar(tg*Yf+.5);
	uchar b = uchar(tb*Yf+.5);
	for (int x = X; x < X+W; x++) {
		*buf++ = r;
		*buf++ = g;
		*buf++ = b;
	}
}

#ifndef FL_DOXYGEN
void Flcc_ValueBox::draw()
{
	if (damage()&FL_DAMAGE_ALL) draw_box();
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();
	c->hsv2rgb(c->hue(),c->saturation(),1.0,tr,tg,tb);
	int x1 = x()+Fl::box_dx(box());
	int yy1 = y()+Fl::box_dy(box());
	int w1 = w()-Fl::box_dw(box());
	int h1 = h()-Fl::box_dh(box());
	if (damage() == FL_DAMAGE_EXPOSE) fl_push_clip(x1,yy1+py,w1,6);
	fl_draw_image(generate_vimage, this, x1, yy1, w1, h1);
	if (damage() == FL_DAMAGE_EXPOSE) fl_pop_clip();
	int Y = int((1-c->value()) * (h1-6));
	if (Y < 0) Y = 0;
	else if (Y > h1-6) Y = h1-6;
	draw_box(FL_UP_BOX,x1,yy1+Y,w1,6,Fl::focus() == this ? FL_FOREGROUND_COLOR : FL_GRAY);
	py = Y;
}
#endif // !FL_DOXYGEN

#ifndef FL_DOXYGEN
int Flcc_ValueBox::handle_key(int key)
{
	int h1 = h()-Fl::box_dh(box())-6;
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)parent();

	int Y = int((1-c->value()) * h1);
	if (Y < 0) Y = 0;
	else if (Y > h1) Y = h1;

	switch (key) {
	case FL_Up :
		Y -= 3;
		break;
	case FL_Down :
		Y += 3;
		break;
	default :
		return 0;
	}

	double Yf;
	Yf = 1-((double)Y/(double)h1);
	if (c->hsv(c->hue(),c->saturation(),Yf)) c->do_callback();

	return 1;
}
#endif // !FL_DOXYGEN

////////////////////////////////////////////////////////////////

void Fl_Color_Chooser::rgb_cb(Fl_Widget* o, void*)
{
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)(o->parent());
	double R = c->rvalue.value();
	double G = c->gvalue.value();
	double B = c->bvalue.value();
	if (c->mode() == M_HSV) {
		if (c->hsv(R,G,B)) c->do_callback();
		return;
	}
	if (c->mode() != M_RGB) {
		R = R/255;
		G = G/255;
		B = B/255;
	}
	if (c->rgb(R,G,B)) c->do_callback();
}

void Fl_Color_Chooser::mode_cb(Fl_Widget* o, void*)
{
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)(o->parent());
	// force them to redraw even if value is the same:
	c->rvalue.value(-1);
	c->gvalue.value(-1);
	c->bvalue.value(-1);
	c->set_valuators();
}

void Fl_Color_Chooser::mode(int newMode)
{
	choice.value(newMode);
	choice.do_callback();
}


////////////////////////////////////////////////////////////////

/**
  Creates a new Fl_Color_Chooser widget using the given position, size, and
  label string.
  The recommended dimensions are 200x95. The color is initialized to black.
  \param[in] X, Y, W, H position and size of the widget
  \param[in] L widget label, default is no label
 */
Fl_Color_Chooser::Fl_Color_Chooser(int X, int Y, int W, int H, const char* L)
	: Fl_Group(0,0,195,115,L),
	  huebox(0,0,115,115),
	  valuebox(115,0,20,115),
	  choice(140,0,55,25),
	  rvalue(140,30,55,25),
	  gvalue(140,60,55,25),
	  bvalue(140,90,55,25),
	  resize_box(0,0,115,115)
{
	end();
	resizable(resize_box);
	resize(X,Y,W,H);
	r_ = g_ = b_ = 0;
	hue_ = 0.0;
	saturation_ = 0.0;
	value_ = 0.0;
	huebox.box(FL_DOWN_FRAME);
	valuebox.box(FL_DOWN_FRAME);
	choice.menu(mode_menu);
	set_valuators();
	rvalue.callback(rgb_cb);
	gvalue.callback(rgb_cb);
	bvalue.callback(rgb_cb);
	choice.callback(mode_cb);
	choice.box(FL_THIN_UP_BOX);
	choice.textfont(FL_HELVETICA_BOLD_ITALIC);
}

////////////////////////////////////////////////////////////////
// fl_color_chooser():

#include "Fl_Window.H"
#include "Fl_Box.H"
#include "Fl_Return_Button.H"

class ColorChip : public Fl_Widget
{
	void draw();
public:
	uchar r,g,b;
	ColorChip(int X, int Y, int W, int H) : Fl_Widget(X,Y,W,H) {
		box(FL_ENGRAVED_FRAME);
	}
};

void ColorChip::draw()
{
	if (damage()&FL_DAMAGE_ALL) draw_box();
	fl_rectf(x()+Fl::box_dx(box()),
	         y()+Fl::box_dy(box()),
	         w()-Fl::box_dw(box()),
	         h()-Fl::box_dh(box()),r,g,b);
}

static void chooser_cb(Fl_Widget* o, void* vv)
{
	Fl_Color_Chooser* c = (Fl_Color_Chooser*)o;
	ColorChip* v = (ColorChip*)vv;
	v->r = uchar(255*c->r()+.5);
	v->g = uchar(255*c->g()+.5);
	v->b = uchar(255*c->b()+.5);
	v->damage(FL_DAMAGE_EXPOSE);
}

extern const char* fl_ok;
extern const char* fl_cancel;

// fl_color_chooser's callback for ok_button (below)
//  [in] o is a pointer to okay_button (below)
//  [in] p is a pointer to an int to receive the return value (1)
// closes the fl_color_chooser window
static void cc_ok_cb (Fl_Widget *o, void *p)
{
	*((int *)p) = 1; // set return value
	o->window()->hide();
}

// fl_color_chooser's callback for cancel_button and window close
//  [in] o is a pointer to cancel_button (below) _or_ the dialog window
//  [in] p is a pointer to an int to receive the return value (0)
// closes the fl_color_chooser window
static void cc_cancel_cb (Fl_Widget *o, void *p)
{
	*((int *)p) = 0; // set return value
	if (o->window()) // cancel button
		o->window()->hide();
	else // window close
		o->hide();
}

/** \addtogroup  group_comdlg
    @{ */
/**
  \brief Pops up a window to let the user pick an arbitrary RGB color.
  \note \#include <Fl_Color_Chooser.H>
  \image html fl_color_chooser.jpg
  \image latex  fl_color_chooser.jpg "fl_color_chooser" width=8cm
  \param[in] name Title label for the window
  \param[in,out] r, g, b Color components in the range 0.0 to 1.0.
  \param[in] cmode Optional mode for color chooser. See mode(int). Default -1 if none (rgb mode).
  \retval 1 if user confirms the selection
  \retval 0 if user cancels the dialog
  \relates Fl_Color_Chooser
 */
int fl_color_chooser(const char* name, double& r, double& g, double& b, int cmode)
{
	int ret = 0;
	Fl_Window window(215,200,name);
	window.callback(cc_cancel_cb,&ret);
	Fl_Color_Chooser chooser(10, 10, 195, 115);
	ColorChip ok_color(10, 130, 95, 25);
	Fl_Return_Button ok_button(10, 165, 95, 25, fl_ok);
	ok_button.callback(cc_ok_cb,&ret);
	ColorChip cancel_color(110, 130, 95, 25);
	cancel_color.r = uchar(255*r+.5);
	ok_color.r = cancel_color.r;
	ok_color.g = cancel_color.g = uchar(255*g+.5);
	ok_color.b = cancel_color.b = uchar(255*b+.5);
	Fl_Button cancel_button(110, 165, 95, 25, fl_cancel);
	cancel_button.callback(cc_cancel_cb,&ret);
	window.resizable(chooser);
	chooser.rgb(r,g,b);
	chooser.callback(chooser_cb, &ok_color);
	if (cmode!=-1) chooser.mode(cmode);
	window.end();
	window.set_modal();
	window.hotspot(window);
	window.show();
	while (window.shown()) Fl::wait();
	if (ret) { // ok_button or Enter
		r = chooser.r();
		g = chooser.g();
		b = chooser.b();
	}
	return ret;
}

/**
  \brief Pops up a window to let the user pick an arbitrary RGB color.
  \note \#include <Fl_Color_Chooser.H>
  \image html fl_color_chooser.jpg
  \image latex  fl_color_chooser.jpg "fl_color_chooser" width=8cm
  \param[in] name Title label for the window
  \param[in,out] r, g, b Color components in the range 0 to 255.
  \param[in] cmode Optional mode for color chooser. See mode(int). Default -1 if none (rgb mode).
  \retval 1 if user confirms the selection
  \retval 0 if user cancels the dialog
  \relates Fl_Color_Chooser
 */
int fl_color_chooser(const char* name, uchar& r, uchar& g, uchar& b, int cmode)
{
	double dr = r/255.0;
	double dg = g/255.0;
	double db = b/255.0;
	if (fl_color_chooser(name,dr,dg,db,cmode)) {
		r = uchar(255*dr+.5);
		g = uchar(255*dg+.5);
		b = uchar(255*db+.5);
		return 1;
	}
	return 0;
}

/** @} */
//
// End of "$Id: Fl_Color_Chooser.cxx 9673 2012-08-18 10:47:48Z AlbrechtS $".
//
